package ink.tiptop.utils;

import cn.hutool.core.lang.Assert;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.StampedLock;

/**
 * @author ink.tiptop.zjf
 * @Title: SpringCloud_Framework
 * @Package ink.tiptop.utils.lock
 * @Description: 锁的案例
 * @date 2020-05-11 13:34
 */
public class LockUtils {

    // TODO 时间标记锁
    private static Map<String, StampedLock> lockMap = new ConcurrentHashMap<>(8);
    private static Map<String, ReentrantReadWriteLock> readWriteLockMap = new ConcurrentHashMap<>(8);
    // 测试
//    private final StampedLock lock = new StampedLock();
    // 以上是一般性的 读写锁
    // TODO 使用 ReadWriteLock 会导致写线程必须等待读线程完成后才能写？这是个坑啊
    //  没错，读的过程中不允许写，我们称这样的锁为悲观锁
    //  乐观锁的特点是，读的过程中也允许写
    //  NPR：“没错，只要我们乐观地读取数据后做一个检查，判断读的过程中是否有写入发生。
    //  如果没有写入，说明我们乐观地获取到的数据是正确的，乐观为我们提高了效率。
    //  如果检查发现读的过程中有写入，说明读到的数据有误，这时我们再使用悲观锁将正确的数据读出来。这样就可以做到性能、稳定兼顾了。”
//    private int number = 0;

    public static StampedLock getLock(String id) {
        Assert.notNull(id);
        synchronized (LockUtils.class) {
            StampedLock lock = lockMap.get(id);
            if (lock == null) {
                lock = new StampedLock();
                lockMap.put(id, lock);
            }
            return lock;
        }
    }

    /**
     * @param id 为雪花算法计算出来的id
     * @return
     */
    public static ReentrantReadWriteLock getReadWriteLock(String id) {
        Assert.notNull(id);
        synchronized (LockUtils.class) {
            ReentrantReadWriteLock lock = readWriteLockMap.get(id);
            if (lock == null) {
                lock = new ReentrantReadWriteLock();
                readWriteLockMap.put(id, lock);
            }
            return lock;
        }
        //        long stamp = lock.writeLock();
//        number += change;
//        System.out.println("写入 " + number);
//        lock.unlockWrite(stamp);
    }

//    private void read() {
//
//        // 尝试乐观读取
//        long stamp = lock.tryOptimisticRead();
//        int readNumber = number;
//        System.out.println("乐观读取到的 number = " + readNumber);
//        // 检查乐观读取到的数据是否有误
//        // 就是这么简单,先用 tryOptimisticRead 尝试乐观读取,再使用 lock.validate(stamp) 验证版本号是否被修改。
//        // 如果被修改了,说明读取有误,则换用悲观锁重新读取即可。
//
//        // 如果读取频繁,写入较少时,使用乐观锁可以减少加锁、解锁的次数；
//        // 但如果写入频繁,使用乐观锁会增加重试次数,反而降低了程序的吞吐量。
//        // 所以总的来说,读取频繁使用乐观锁,写入频繁使用悲观锁。
//        if (!lock.validate(stamp)) {
//            stamp = lock.readLock();
//            System.out.println("乐观读取到的 number " + readNumber + " 有误,换用悲观锁重新读取:number = " + number);
//            lock.unlockRead(stamp);
//        }
//
//    }
//
//    private void write(int change) {
//        long stamp = lock.writeLock();
//        number += change;
//        System.out.println("写入 " + number);
//        lock.unlockWrite(stamp);
//    }
//
//
//    public void test() throws InterruptedException {
//        // 开启一个线程写入 100 次 number
//        new Thread(() -> {
//            for (int i = 0; i < 100; i++) {
//                write(1);
//            }
//        }).start();
//        // 开启一个线程读取 100 次 number
//        new Thread(() -> {
//            for (int i = 0; i < 100; i++) {
//                read();
//            }
//        }).start();
//
//        // 睡眠一秒保证线程执行完成
//        Thread.sleep(1000);
//    }

}
